package me.zxingcore.qrcode;

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.support.v4.util.ArrayMap;

import java.util.Map;

import me.zxingcore.BarcodeFormat;
import me.zxingcore.EncodeHintType;
import me.zxingcore.WriterException;
import me.zxingcore.common.BitMatrix;
import me.zxingcore.qrcode.decoder.ErrorCorrectionLevel;

public class QRCodeEncoder {

    private int qrWidth;
    private int qrHeight;
    private String content;

    private QRCodeParams mParams;

    private QRCodeEncoder(String content, int qrWidth, int qrHeight, QRCodeParams params) {
        this.qrWidth = qrWidth;
        this.qrHeight = qrHeight;
        this.content = content;
        this.mParams = params;
    }

    public Builder newBuilder() {
        return new Builder(this);
    }

    public Bitmap generateQRCode() {
        return createQRCode(content, qrWidth, qrHeight, mParams);
    }

    private Bitmap createQRCode(String content, int targetW, int targetH, QRCodeParams params) {

        BitMatrix bitMatrix = getBitMatrix(content, targetW, targetH, params.hints);

        int bitWidth = bitMatrix.getWidth();
        int bitHeight = bitMatrix.getHeight();

        int[] pixels = new int[bitWidth * bitHeight];
        for (int y = 0; y < bitHeight; y++) {
            for (int x = 0; x < bitWidth; x++) {
                if (bitMatrix.get(x, y)) {
                    pixels[y * bitWidth + x] = params.onColor;
                } else {
                    pixels[y * bitWidth + x] = params.offColor;
                }
            }
        }

        Bitmap bitmap = Bitmap.createBitmap(bitWidth, bitHeight, Bitmap.Config.ARGB_8888);
        bitmap.setPixels(pixels, 0, bitWidth, 0, 0, bitWidth, bitHeight);
        bitmap = scaleBitmap(bitmap, targetW, targetH);

        int margin = 0;
        Bitmap target = bitmap;
        if (params.hints != null) {
            if (params.hints.containsKey(EncodeHintType.MARGIN)) {
                margin = Integer.parseInt(params.hints.get(EncodeHintType.MARGIN).toString());
                target = Bitmap.createBitmap(bitmap.getWidth() + margin * 2,bitmap.getHeight() + margin * 2, Bitmap.Config.ARGB_8888);
                target.eraseColor(params.paddingColor);

                Canvas canvas = new Canvas(target);
                canvas.drawBitmap(bitmap, margin, margin, null);

            }

        }

        if (params.logo != null) {
            drawLogo(target, bitmap, params);
        }

        return target;

    }

    private BitMatrix getBitMatrix(String content, int width, int height, Map<EncodeHintType, ?> hints) {

        try {
            return new QRCodeWriter().encode(content, BarcodeFormat.QR_CODE, width, height, hints);
        } catch (WriterException e) {
            e.printStackTrace();
        }
        return null;
    }

    private Bitmap scaleBitmap(Bitmap bitmap, int targetWidth, int targetHeight) {
        Matrix matrix = new Matrix();
        matrix.postScale(targetWidth * 1f / bitmap.getWidth(), targetHeight * 1f / bitmap.getHeight());
        return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
    }

    private void drawLogo(Bitmap target, Bitmap qrBitmap, QRCodeParams params) {

        ErrorCorrectionLevel errorLevel = ErrorCorrectionLevel.valueOf(params.hints.get(EncodeHintType.ERROR_CORRECTION).toString());
        float rate = getErrorLevelRate(errorLevel);
        float maxWidth = qrBitmap.getWidth() * rate;
        float maxHeight = qrBitmap.getHeight() * rate;

        Bitmap logo = params.logo;
        int logoW = params.logoWidth > 0 ? params.logoWidth : logo.getWidth();
        int logoH = params.logoHeight > 0 ? params.logoHeight : logo.getHeight();

        out : {
            if (logoW > maxWidth || logoH > maxHeight) {
                logoW = logoW > maxWidth ? (int) maxWidth : logoW;
                logoH = logoH > maxHeight ? (int) maxHeight : logoH;
                logo = scaleBitmap(logo, logoW, logoH);
                break out;
            }
            if (logoW < logo.getWidth() || logoH < logo.getHeight()) {
                logo = scaleBitmap(logo, logoW, logoH);
            }
        }

        float left = (target.getWidth() - logoW) / 2f;
        float top = (target.getHeight() - logoH) / 2f;
        Canvas canvas = new Canvas(target);
        canvas.drawBitmap(logo, left, top, null);

    }

    private float getErrorLevelRate(ErrorCorrectionLevel level) {
        if (level == ErrorCorrectionLevel.L) {
            return 0.07f;
        }
        if (level == ErrorCorrectionLevel.M) {
            return 0.15f;
        }
        if (level == ErrorCorrectionLevel.Q) {
            return 0.25f;
        }
        if (level == ErrorCorrectionLevel.H) {
            return 0.30f;
        }
        return 0.30f;
    }

    private static class QRCodeParams {
        public Map<EncodeHintType, Object> hints;

        public int logoWidth;
        public int logoHeight;
        public Bitmap logo;

        public int onColor = 0xff000000;
        public int offColor = 0xffffffff;
        public int paddingColor = 0xffffffff;

    }


    public static final class Builder {

        private int width = 260;
        private int height = 260;
        private String content;

        private QRCodeParams params;

        public Builder() {
            params = new QRCodeParams();

            Map<EncodeHintType, Object> hints = new ArrayMap<>();
            hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
            hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
            hints.put(EncodeHintType.MARGIN, 0);
            params.hints = hints;

        }

        Builder(QRCodeEncoder encoder) {
            params = encoder.mParams;
        }

        public Builder setPadding(int padding) {
            params.hints.put(EncodeHintType.MARGIN, padding);
            return this;
        }

        public Builder setPaddingColor(int paddingColor) {
            params.paddingColor = paddingColor;
            return this;
        }

        public Builder setWidthHeight(int width, int height) {
            this.width = width;
            this.height = height;
            return this;
        }

        public Builder setContent(String content) {
            this.content = content;
            return this;
        }

        public Builder setEncode(String encode) {
            params.hints.put(EncodeHintType.CHARACTER_SET, encode);
            return this;
        }

        public Builder setErrorLevel(ErrorCorrectionLevel errorLevel) {
            params.hints.put(EncodeHintType.ERROR_CORRECTION, errorLevel);
            return this;
        }

        public Builder setHints(Map<EncodeHintType, Object> hints) {
            params.hints = hints;
            return this;
        }

        public Builder setLogoWH(int logoWidth, int logoHeight) {
            params.logoWidth = logoWidth;
            params.logoHeight = logoHeight;
            return this;
        }

        public Builder setLogo(Bitmap logo) {
            params.logo = logo;
            return this;
        }

        public Builder setOnColor(int onColor) {
            params.onColor = onColor;
            return this;
        }

        public Builder setOffColor(int offColor) {
            params.offColor = offColor;
            return this;
        }

        public QRCodeEncoder build() {
            return new QRCodeEncoder(content, width, height, params);
        }

    }
}
